/*
 * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * Copyright 2005 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sun.org.apache.xerces.internal.impl;

import com.sun.xml.internal.stream.dtd.nonvalidating.DTDGrammar;
import java.io.EOFException;
import java.io.IOException;

import com.sun.org.apache.xerces.internal.impl.msg.XMLMessageFormatter;

import com.sun.org.apache.xerces.internal.util.SymbolTable;
import com.sun.org.apache.xerces.internal.util.XMLAttributesImpl;
import com.sun.org.apache.xerces.internal.util.XMLChar;
import com.sun.org.apache.xerces.internal.util.XMLStringBuffer;

import com.sun.org.apache.xerces.internal.xni.XMLDTDContentModelHandler;
import com.sun.org.apache.xerces.internal.xni.XMLDTDHandler;
import com.sun.org.apache.xerces.internal.xni.XMLResourceIdentifier;
import com.sun.org.apache.xerces.internal.xni.XMLString;
import com.sun.org.apache.xerces.internal.xni.XNIException;
import com.sun.org.apache.xerces.internal.xni.parser.XMLComponent;
import com.sun.org.apache.xerces.internal.xni.parser.XMLComponentManager;
import com.sun.org.apache.xerces.internal.xni.parser.XMLConfigurationException;
import com.sun.org.apache.xerces.internal.xni.parser.XMLDTDScanner;
import com.sun.org.apache.xerces.internal.xni.parser.XMLInputSource;
import com.sun.org.apache.xerces.internal.xni.Augmentations;
import com.sun.org.apache.xerces.internal.impl.XMLErrorReporter;
import com.sun.org.apache.xerces.internal.impl.XMLEntityHandler;
import com.sun.org.apache.xerces.internal.impl.Constants;
import com.sun.org.apache.xerces.internal.utils.XMLLimitAnalyzer;
import com.sun.org.apache.xerces.internal.utils.XMLSecurityManager;
import com.sun.xml.internal.stream.Entity;

/**
 * This class is responsible for scanning the declarations found
 * in the internal and external subsets of a DTD in an XML document.
 * The scanner acts as the sources for the DTD information which is
 * communicated to the DTD handlers.
 * <p>
 * This component requires the following features and properties from the
 * component manager that uses it:
 * <ul>
 * <li>http://xml.org/sax/features/validation</li>
 * <li>http://apache.org/xml/features/scanner/notify-char-refs</li>
 * <li>http://apache.org/xml/properties/internal/symbol-table</li>
 * <li>http://apache.org/xml/properties/internal/error-reporter</li>
 * <li>http://apache.org/xml/properties/internal/entity-manager</li>
 * </ul>
 *
 * @author Arnaud  Le Hors, IBM
 * @author Andy Clark, IBM
 * @author Glenn Marcy, IBM
 * @author Eric Ye, IBM
 * @version $Id: XMLDTDScannerImpl.java,v 1.8 2010-11-01 04:39:41 joehw Exp $
 */
public class XMLDTDScannerImpl
    extends XMLScanner
    implements XMLDTDScanner, XMLComponent, XMLEntityHandler {

  //
  // Constants
  //

  // scanner states

  /**
   * Scanner state: end of input.
   */
  protected static final int SCANNER_STATE_END_OF_INPUT = 0;

  /**
   * Scanner state: text declaration.
   */
  protected static final int SCANNER_STATE_TEXT_DECL = 1;

  /**
   * Scanner state: markup declaration.
   */
  protected static final int SCANNER_STATE_MARKUP_DECL = 2;

  // recognized features and properties

  /**
   * Recognized features.
   */
  private static final String[] RECOGNIZED_FEATURES = {
      VALIDATION,
      NOTIFY_CHAR_REFS,
  };

  /**
   * Feature defaults.
   */
  private static final Boolean[] FEATURE_DEFAULTS = {
      null,
      Boolean.FALSE,
  };

  /**
   * Recognized properties.
   */
  private static final String[] RECOGNIZED_PROPERTIES = {
      SYMBOL_TABLE,
      ERROR_REPORTER,
      ENTITY_MANAGER,
  };

  /**
   * Property defaults.
   */
  private static final Object[] PROPERTY_DEFAULTS = {
      null,
      null,
      null,
  };

  // debugging

  /**
   * Debug scanner state.
   */
  private static final boolean DEBUG_SCANNER_STATE = false;

  //
  // Data
  //

  // handlers

  /**
   * DTD handler.
   */
  public XMLDTDHandler fDTDHandler = null;

  /**
   * DTD content model handler.
   */
  protected XMLDTDContentModelHandler fDTDContentModelHandler;

  // state

  /**
   * Scanner state.
   */
  protected int fScannerState;

  /**
   * Standalone.
   */
  protected boolean fStandalone;

  /**
   * Seen external DTD.
   */
  protected boolean fSeenExternalDTD;

  /**
   * Seen external parameter entity.
   */
  protected boolean fSeenExternalPE;

  // private data

  /**
   * Start DTD called.
   */
  private boolean fStartDTDCalled;

  /**
   * Default attribute
   */
  private XMLAttributesImpl fAttributes = new XMLAttributesImpl();

  /**
   * Stack of content operators (either '|' or ',') in children
   * content.
   */
  private int[] fContentStack = new int[5];

  /**
   * Size of content stack.
   */
  private int fContentDepth;

  /**
   * Parameter entity stack to check well-formedness.
   */
  private int[] fPEStack = new int[5];


  /**
   * Parameter entity stack to report start/end entity calls.
   */
  private boolean[] fPEReport = new boolean[5];

  /**
   * Number of opened parameter entities.
   */
  private int fPEDepth;

  /**
   * Markup depth.
   */
  private int fMarkUpDepth;

  /**
   * Number of opened external entities.
   */
  private int fExtEntityDepth;

  /**
   * Number of opened include sections.
   */
  private int fIncludeSectDepth;

  // temporary variables

  /**
   * Array of 3 strings.
   */
  private String[] fStrings = new String[3];

  /**
   * String.
   */
  private XMLString fString = new XMLString();

  /**
   * String buffer.
   */
  private XMLStringBuffer fStringBuffer = new XMLStringBuffer();

  /**
   * String buffer.
   */
  private XMLStringBuffer fStringBuffer2 = new XMLStringBuffer();

  /**
   * Literal text.
   */
  private XMLString fLiteral = new XMLString();

  /**
   * Literal text.
   */
  private XMLString fLiteral2 = new XMLString();

  /**
   * Enumeration values.
   */
  private String[] fEnumeration = new String[5];

  /**
   * Enumeration values count.
   */
  private int fEnumerationCount;

  /**
   * Ignore conditional section buffer.
   */
  private XMLStringBuffer fIgnoreConditionalBuffer = new XMLStringBuffer(128);

  /**
   * Object contains grammar information for a non-validaing parser.
   */
  DTDGrammar nvGrammarInfo = null;

  boolean nonValidatingMode = false;
  //
  // Constructors
  //

  /**
   * Default constructor.
   */
  public XMLDTDScannerImpl() {
  } // <init>()

  /**
   * Constructor for he use of non-XMLComponentManagers.
   */
  public XMLDTDScannerImpl(SymbolTable symbolTable,
      XMLErrorReporter errorReporter, XMLEntityManager entityManager) {
    fSymbolTable = symbolTable;
    fErrorReporter = errorReporter;
    fEntityManager = entityManager;
    entityManager.setProperty(SYMBOL_TABLE, fSymbolTable);
  }

  //
  // XMLDTDScanner methods
  //

  /**
   * Sets the input source.
   *
   * @param inputSource The input source or null.
   * @throws IOException Thrown on i/o error.
   */
  public void setInputSource(XMLInputSource inputSource) throws IOException {
    if (inputSource == null) {
      // no system id was available
      if (fDTDHandler != null) {
        fDTDHandler.startDTD(null, null);
        fDTDHandler.endDTD(null);
      }
      if (nonValidatingMode) {
        nvGrammarInfo.startDTD(null, null);
        nvGrammarInfo.endDTD(null);
      }
      return;
    }
    fEntityManager.setEntityHandler(this);
    fEntityManager.startDTDEntity(inputSource);
  } // setInputSource(XMLInputSource)


  public void setLimitAnalyzer(XMLLimitAnalyzer limitAnalyzer) {
    fLimitAnalyzer = limitAnalyzer;
  }

  /**
   * Scans the external subset of the document.
   *
   * @param complete True if the scanner should scan the document completely, pushing all events to
   * the registered document handler. A value of false indicates that that the scanner should only
   * scan the next portion of the document and return. A scanner instance is permitted to completely
   * scan a document if it does not support this "pull" scanning model.
   * @return True if there is more to scan, false otherwise.
   */
  public boolean scanDTDExternalSubset(boolean complete)
      throws IOException, XNIException {

    fEntityManager.setEntityHandler(this);
    if (fScannerState == SCANNER_STATE_TEXT_DECL) {
      fSeenExternalDTD = true;
      boolean textDecl = scanTextDecl();
      if (fScannerState == SCANNER_STATE_END_OF_INPUT) {
        return false;
      } else {
        // next state is markup decls regardless of whether there
        // is a TextDecl or not
        setScannerState(SCANNER_STATE_MARKUP_DECL);
        if (textDecl && !complete) {
          return true;
        }
      }
    }
    // keep dispatching "events"
    do {
      if (!scanDecls(complete)) {
        return false;
      }
    } while (complete);

    // return that there is more to scan
    return true;

  } // scanDTDExternalSubset(boolean):boolean

  /**
   * Scans the internal subset of the document.
   *
   * @param complete True if the scanner should scan the document completely, pushing all events to
   * the registered document handler. A value of false indicates that that the scanner should only
   * scan the next portion of the document and return. A scanner instance is permitted to completely
   * scan a document if it does not support this "pull" scanning model.
   * @param standalone True if the document was specified as standalone. This value is important for
   * verifying certain well-formedness constraints.
   * @param hasExternalDTD True if the document has an external DTD. This allows the scanner to
   * properly notify the handler of the end of the DTD in the absence of an external subset.
   * @return True if there is more to scan, false otherwise.
   */
  public boolean scanDTDInternalSubset(boolean complete, boolean standalone,
      boolean hasExternalSubset)
      throws IOException, XNIException {
    // reset entity scanner
    //xxx:stax getText() is supposed to return only DTD internal subset
    //shouldn't we record position here before we go ahead ??

    fEntityScanner = (XMLEntityScanner) fEntityManager.getEntityScanner();
    fEntityManager.setEntityHandler(this);
    fStandalone = standalone;
    //System.out.println("state"+fScannerState);
    if (fScannerState == SCANNER_STATE_TEXT_DECL) {
      // call handler
      if (fDTDHandler != null) {
        fDTDHandler.startDTD(fEntityScanner, null);
        fStartDTDCalled = true;
      }

      if (nonValidatingMode) {
        fStartDTDCalled = true;
        nvGrammarInfo.startDTD(fEntityScanner, null);
      }
      // set starting state for internal subset
      setScannerState(SCANNER_STATE_MARKUP_DECL);
    }
    // keep dispatching "events"
    do {
      if (!scanDecls(complete)) {
        // call handler
        if (fDTDHandler != null && hasExternalSubset == false) {
          fDTDHandler.endDTD(null);
        }
        if (nonValidatingMode && hasExternalSubset == false) {
          nvGrammarInfo.endDTD(null);
        }
        // we're done, set starting state for external subset
        setScannerState(SCANNER_STATE_TEXT_DECL);
        return false;
      }
    } while (complete);

    // return that there is more to scan
    return true;

  } // scanDTDInternalSubset(boolean,boolean,boolean):boolean

  //
  // XMLComponent methods
  //

  /**
   * reset
   */
  public void reset(XMLComponentManager componentManager)
      throws XMLConfigurationException {

    super.reset(componentManager);
    init();

  } // reset(XMLComponentManager)

  // this is made for something like XMLDTDLoader--XMLComponentManager-free operation...
  public void reset() {
    super.reset();
    init();

  }

  public void reset(PropertyManager props) {
    setPropertyManager(props);
    super.reset(props);
    init();
    nonValidatingMode = true;
    //Revisit : Create new grammar until we implement GrammarPool.
    nvGrammarInfo = new DTDGrammar(fSymbolTable);
  }

  /**
   * Returns a list of feature identifiers that are recognized by
   * this component. This method may return null if no features
   * are recognized by this component.
   */
  public String[] getRecognizedFeatures() {
    return (String[]) (RECOGNIZED_FEATURES.clone());
  } // getRecognizedFeatures():String[]

  /**
   * Returns a list of property identifiers that are recognized by
   * this component. This method may return null if no properties
   * are recognized by this component.
   */
  public String[] getRecognizedProperties() {
    return (String[]) (RECOGNIZED_PROPERTIES.clone());
  } // getRecognizedProperties():String[]

  /**
   * Returns the default state for a feature, or null if this
   * component does not want to report a default value for this
   * feature.
   *
   * @param featureId The feature identifier.
   * @since Xerces 2.2.0
   */
  public Boolean getFeatureDefault(String featureId) {
    for (int i = 0; i < RECOGNIZED_FEATURES.length; i++) {
      if (RECOGNIZED_FEATURES[i].equals(featureId)) {
        return FEATURE_DEFAULTS[i];
      }
    }
    return null;
  } // getFeatureDefault(String):Boolean

  /**
   * Returns the default state for a property, or null if this
   * component does not want to report a default value for this
   * property.
   *
   * @param propertyId The property identifier.
   * @since Xerces 2.2.0
   */
  public Object getPropertyDefault(String propertyId) {
    for (int i = 0; i < RECOGNIZED_PROPERTIES.length; i++) {
      if (RECOGNIZED_PROPERTIES[i].equals(propertyId)) {
        return PROPERTY_DEFAULTS[i];
      }
    }
    return null;
  } // getPropertyDefault(String):Object

  //
  // XMLDTDSource methods
  //

  /**
   * setDTDHandler
   */
  public void setDTDHandler(XMLDTDHandler dtdHandler) {
    fDTDHandler = dtdHandler;
  } // setDTDHandler(XMLDTDHandler)

  /**
   * getDTDHandler
   *
   * @return the XMLDTDHandler
   */
  public XMLDTDHandler getDTDHandler() {
    return fDTDHandler;
  } // getDTDHandler():  XMLDTDHandler

  //
  // XMLDTDContentModelSource methods
  //

  /**
   * setDTDContentModelHandler
   */
  public void setDTDContentModelHandler(XMLDTDContentModelHandler
      dtdContentModelHandler) {
    fDTDContentModelHandler = dtdContentModelHandler;
  } // setDTDContentModelHandler

  /**
   * getDTDContentModelHandler
   *
   * @return XMLDTDContentModelHandler
   */
  public XMLDTDContentModelHandler getDTDContentModelHandler() {
    return fDTDContentModelHandler;
  } // setDTDContentModelHandler

  //
  // XMLEntityHandler methods
  //

  /**
   * This method notifies of the start of an entity. The DTD has the
   * pseudo-name of "[dtd]" parameter entity names start with '%'; and
   * general entities are just specified by their name.
   *
   * @param name The name of the entity.
   * @param identifier The resource identifier.
   * @param encoding The auto-detected IANA encoding name of the entity stream. This value will be
   * null in those situations where the entity encoding is not auto-detected (e.g. internal entities
   * or a document entity that is parsed from a java.io.Reader).
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void startEntity(String name,
      XMLResourceIdentifier identifier,
      String encoding, Augmentations augs) throws XNIException {

    super.startEntity(name, identifier, encoding, augs);

    boolean dtdEntity = name.equals("[dtd]");
    if (dtdEntity) {
      // call handler
      if (fDTDHandler != null && !fStartDTDCalled) {
        fDTDHandler.startDTD(fEntityScanner, null);
      }
      if (fDTDHandler != null) {
        fDTDHandler.startExternalSubset(identifier, null);
      }
      fEntityManager.startExternalSubset();
      fEntityStore.startExternalSubset();
      fExtEntityDepth++;
    } else if (name.charAt(0) == '%') {
      pushPEStack(fMarkUpDepth, fReportEntity);
      if (fEntityScanner.isExternal()) {
        fExtEntityDepth++;
      }
    }

    // call handler
    if (fDTDHandler != null && !dtdEntity && fReportEntity) {
      fDTDHandler.startParameterEntity(name, identifier, encoding, null);
    }

  } // startEntity(String,XMLResourceIdentifier,String)

  /**
   * This method notifies the end of an entity. The DTD has the pseudo-name
   * of "[dtd]" parameter entity names start with '%'; and general entities
   * are just specified by their name.
   *
   * @param name The name of the entity.
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void endEntity(String name, Augmentations augs)
      throws XNIException, IOException {

    super.endEntity(name, augs);

    // if there is no data after the doctype
    //
    if (fScannerState == SCANNER_STATE_END_OF_INPUT) {
      return;
    }

    // Handle end of PE
    boolean reportEntity = fReportEntity;
    if (name.startsWith("%")) {
      reportEntity = peekReportEntity();
      // check well-formedness of the enity
      int startMarkUpDepth = popPEStack();
      // throw fatalError if this entity was incomplete and
      // was a freestanding decl
      if (startMarkUpDepth == 0 &&
          startMarkUpDepth < fMarkUpDepth) {
        fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
            "ILL_FORMED_PARAMETER_ENTITY_WHEN_USED_IN_DECL",
            new Object[]{fEntityManager.fCurrentEntity.name},
            XMLErrorReporter.SEVERITY_FATAL_ERROR);
      }
      if (startMarkUpDepth != fMarkUpDepth) {
        reportEntity = false;
        if (fValidation) {
          // Proper nesting of parameter entities is a Validity Constraint
          // and must not be enforced when validation is off
          fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
              "ImproperDeclarationNesting",
              new Object[]{name},
              XMLErrorReporter.SEVERITY_ERROR);
        }
      }
      if (fEntityScanner.isExternal()) {
        fExtEntityDepth--;
      }
    }

    // call handler
    boolean dtdEntity = name.equals("[dtd]");
    if (fDTDHandler != null && !dtdEntity && reportEntity) {
      fDTDHandler.endParameterEntity(name, null);
    }

    // end DTD
    if (dtdEntity) {
      if (fIncludeSectDepth != 0) {
        reportFatalError("IncludeSectUnterminated", null);
      }
      fScannerState = SCANNER_STATE_END_OF_INPUT;
      // call handler
      fEntityManager.endExternalSubset();
      fEntityStore.endExternalSubset();

      if (fDTDHandler != null) {
        fDTDHandler.endExternalSubset(null);
        fDTDHandler.endDTD(null);
      }
      fExtEntityDepth--;
    }

    //XML (Document Entity) is the last opened entity, however
    //if for some reason DTD Scanner receives this callback
    //there is something wrong (probably invalid XML), throw exception.
    //or
    //For standalone DTD loader, it might be the last opened entity
    //and if this is the last opened entity and fMarkUpDepth != 0 or
    //fIncludeSectDepth != 0 or fExtEntityDepth != 0 throw Exception
    if (augs != null && Boolean.TRUE.equals(augs.getItem(Constants.LAST_ENTITY))
        && (fMarkUpDepth != 0 || fExtEntityDepth != 0 || fIncludeSectDepth != 0)) {
      throw new EOFException();
    }

  } // endEntity(String)

  // helper methods

  /**
   * Sets the scanner state.
   *
   * @param state The new scanner state.
   */
  protected final void setScannerState(int state) {

    fScannerState = state;
    if (DEBUG_SCANNER_STATE) {
      System.out.print("### setScannerState: ");
      System.out.print(getScannerStateName(state));
      //System.out.println();
    }

  } // setScannerState(int)

  //
  // Private methods
  //

  /**
   * Returns the scanner state name.
   */
  private static String getScannerStateName(int state) {

    if (DEBUG_SCANNER_STATE) {
      switch (state) {
        case SCANNER_STATE_END_OF_INPUT:
          return "SCANNER_STATE_END_OF_INPUT";
        case SCANNER_STATE_TEXT_DECL:
          return "SCANNER_STATE_TEXT_DECL";
        case SCANNER_STATE_MARKUP_DECL:
          return "SCANNER_STATE_MARKUP_DECL";
      }
    }

    return "??? (" + state + ')';

  } // getScannerStateName(int):String

  protected final boolean scanningInternalSubset() {
    return fExtEntityDepth == 0;
  }

  /**
   * start a parameter entity dealing with the textdecl if there is any
   *
   * @param name The name of the parameter entity to start (without the '%')
   * @param literal Whether this is happening within a literal
   */
  protected void startPE(String name, boolean literal)
      throws IOException, XNIException {
    int depth = fPEDepth;
    String pName = "%" + name;
    if (fValidation && !fEntityStore.isDeclaredEntity(pName)) {
      fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN, "EntityNotDeclared",
          new Object[]{name}, XMLErrorReporter.SEVERITY_ERROR);
    }
    fEntityManager.startEntity(fSymbolTable.addSymbol(pName),
        literal);
    // if we actually got a new entity and it's external
    // parse text decl if there is any
    if (depth != fPEDepth && fEntityScanner.isExternal()) {
      scanTextDecl();
    }
  }

  /**
   * Dispatch an XML "event".
   *
   * @param complete True if this method is intended to scan and dispatch as much as possible.
   * @return True if a TextDecl was scanned.
   * @throws IOException Thrown on i/o error.
   * @throws XNIException Thrown on parse error.
   */
  protected final boolean scanTextDecl()
      throws IOException, XNIException {

    // scan XMLDecl
    boolean textDecl = false;
    if (fEntityScanner.skipString("<?xml")) {
      fMarkUpDepth++;
      // NOTE: special case where document starts with a PI
      //       whose name starts with "xml" (e.g. "xmlfoo")
      if (isValidNameChar(fEntityScanner.peekChar())) {
        fStringBuffer.clear();
        fStringBuffer.append("xml");
        while (isValidNameChar(fEntityScanner.peekChar())) {
          fStringBuffer.append((char) fEntityScanner.scanChar());
        }
        String target =
            fSymbolTable.addSymbol(fStringBuffer.ch,
                fStringBuffer.offset,
                fStringBuffer.length);
        scanPIData(target, fString);
      }

      // standard Text declaration
      else {
        // pseudo-attribute values
        String version = null;
        String encoding = null;

        scanXMLDeclOrTextDecl(true, fStrings);
        textDecl = true;
        fMarkUpDepth--;

        version = fStrings[0];
        encoding = fStrings[1];

        fEntityScanner.setEncoding(encoding);

        // call handler
        if (fDTDHandler != null) {
          fDTDHandler.textDecl(version, encoding, null);
        }
      }
    }
    fEntityManager.fCurrentEntity.mayReadChunks = true;

    return textDecl;

  } // scanTextDecl(boolean):boolean

  /**
   * Scans a processing data. This is needed to handle the situation
   * where a document starts with a processing instruction whose
   * target name <em>starts with</em> "xml". (e.g. xmlfoo)
   *
   * @param target The PI target
   * @param data The string to fill in with the data
   */
  protected final void scanPIData(String target, XMLString data)
      throws IOException, XNIException {
    //Venu REVISIT
    //      super.scanPIData(target, data);
    fMarkUpDepth--;

    // call handler
    if (fDTDHandler != null) {
      fDTDHandler.processingInstruction(target, data, null);
    }

  } // scanPIData(String)

  /**
   * Scans a comment.
   * <p>
   * <pre>
   * [15] Comment ::= '&lt!--' ((Char - '-') | ('-' (Char - '-')))* '-->'
   * </pre>
   * <p>
   * <strong>Note:</strong> Called after scanning past '&lt;!--'
   */
  protected final void scanComment() throws IOException, XNIException {

    fReportEntity = false;
    scanComment(fStringBuffer);
    fMarkUpDepth--;

    // call handler
    if (fDTDHandler != null) {
      fDTDHandler.comment(fStringBuffer, null);
    }
    fReportEntity = true;

  } // scanComment()

  /**
   * Scans an element declaration
   * <p>
   * <pre>
   * [45]    elementdecl    ::=    '&lt;!ELEMENT' S Name S contentspec S? '>'
   * [46]    contentspec    ::=    'EMPTY' | 'ANY' | Mixed | children
   * </pre>
   * <p>
   * <strong>Note:</strong> Called after scanning past '&lt;!ELEMENT'
   */
  protected final void scanElementDecl() throws IOException, XNIException {

    // spaces
    fReportEntity = false;
    if (!skipSeparator(true, !scanningInternalSubset())) {
      reportFatalError("MSG_SPACE_REQUIRED_BEFORE_ELEMENT_TYPE_IN_ELEMENTDECL",
          null);
    }

    // element name
    String name = fEntityScanner.scanName();
    if (name == null) {
      reportFatalError("MSG_ELEMENT_TYPE_REQUIRED_IN_ELEMENTDECL",
          null);
    }

    // spaces
    if (!skipSeparator(true, !scanningInternalSubset())) {
      reportFatalError("MSG_SPACE_REQUIRED_BEFORE_CONTENTSPEC_IN_ELEMENTDECL",
          new Object[]{name});
    }

    // content model
    if (fDTDContentModelHandler != null) {
      fDTDContentModelHandler.startContentModel(name, null);
    }
    String contentModel = null;
    fReportEntity = true;
    if (fEntityScanner.skipString("EMPTY")) {
      contentModel = "EMPTY";
      // call handler
      if (fDTDContentModelHandler != null) {
        fDTDContentModelHandler.empty(null);
      }
    } else if (fEntityScanner.skipString("ANY")) {
      contentModel = "ANY";
      // call handler
      if (fDTDContentModelHandler != null) {
        fDTDContentModelHandler.any(null);
      }
    } else {
      if (!fEntityScanner.skipChar('(')) {
        reportFatalError("MSG_OPEN_PAREN_OR_ELEMENT_TYPE_REQUIRED_IN_CHILDREN",
            new Object[]{name});
      }
      if (fDTDContentModelHandler != null) {
        fDTDContentModelHandler.startGroup(null);
      }
      fStringBuffer.clear();
      fStringBuffer.append('(');
      fMarkUpDepth++;
      skipSeparator(false, !scanningInternalSubset());

      // Mixed content model
      if (fEntityScanner.skipString("#PCDATA")) {
        scanMixed(name);
      } else {              // children content
        scanChildren(name);
      }
      contentModel = fStringBuffer.toString();
    }

    // call handler
    if (fDTDContentModelHandler != null) {
      fDTDContentModelHandler.endContentModel(null);
    }

    fReportEntity = false;
    skipSeparator(false, !scanningInternalSubset());
    // end
    if (!fEntityScanner.skipChar('>')) {
      reportFatalError("ElementDeclUnterminated", new Object[]{name});
    }
    fReportEntity = true;
    fMarkUpDepth--;

    // call handler
    if (fDTDHandler != null) {
      fDTDHandler.elementDecl(name, contentModel, null);
    }
    if (nonValidatingMode) {
      nvGrammarInfo.elementDecl(name, contentModel, null);
    }
  } // scanElementDecl()

  /**
   * scan Mixed content model
   * This assumes the content model has been parsed up to #PCDATA and
   * can simply append to fStringBuffer.
   * <pre>
   * [51]    Mixed    ::=    '(' S? '#PCDATA' (S? '|' S? Name)* S? ')*'
   *                       | '(' S? '#PCDATA' S? ')'
   * </pre>
   *
   * @param elName The element type name this declaration is about.
   *
   * <strong>Note:</strong> Called after scanning past '(#PCDATA'.
   */
  private final void scanMixed(String elName)
      throws IOException, XNIException {

    String childName = null;

    fStringBuffer.append("#PCDATA");
    // call handler
    if (fDTDContentModelHandler != null) {
      fDTDContentModelHandler.pcdata(null);
    }
    skipSeparator(false, !scanningInternalSubset());
    while (fEntityScanner.skipChar('|')) {
      fStringBuffer.append('|');
      // call handler
      if (fDTDContentModelHandler != null) {
        fDTDContentModelHandler.separator(XMLDTDContentModelHandler.SEPARATOR_CHOICE,
            null);
      }
      skipSeparator(false, !scanningInternalSubset());

      childName = fEntityScanner.scanName();
      if (childName == null) {
        reportFatalError("MSG_ELEMENT_TYPE_REQUIRED_IN_MIXED_CONTENT",
            new Object[]{elName});
      }
      fStringBuffer.append(childName);
      // call handler
      if (fDTDContentModelHandler != null) {
        fDTDContentModelHandler.element(childName, null);
      }
      skipSeparator(false, !scanningInternalSubset());
    }
    // The following check must be done in a single call (as opposed to one
    // for ')' and then one for '*') to guarantee that callbacks are
    // properly nested. We do not want to trigger endEntity too early in
    // case we cross the boundary of an entity between the two characters.
    if (fEntityScanner.skipString(")*")) {
      fStringBuffer.append(")*");
      // call handler
      if (fDTDContentModelHandler != null) {
        fDTDContentModelHandler.endGroup(null);
        fDTDContentModelHandler.occurrence(XMLDTDContentModelHandler.OCCURS_ZERO_OR_MORE,
            null);
      }
    } else if (childName != null) {
      reportFatalError("MixedContentUnterminated",
          new Object[]{elName});
    } else if (fEntityScanner.skipChar(')')) {
      fStringBuffer.append(')');
      // call handler
      if (fDTDContentModelHandler != null) {
        fDTDContentModelHandler.endGroup(null);
      }
    } else {
      reportFatalError("MSG_CLOSE_PAREN_REQUIRED_IN_CHILDREN",
          new Object[]{elName});
    }
    fMarkUpDepth--;
    // we are done
  }

  /**
   * scan children content model
   * This assumes it can simply append to fStringBuffer.
   * <pre>
   * [47]    children  ::=    (choice | seq) ('?' | '*' | '+')?
   * [48]    cp        ::=    (Name | choice | seq) ('?' | '*' | '+')?
   * [49]    choice    ::=    '(' S? cp ( S? '|' S? cp )+ S? ')'
   * [50]    seq       ::=    '(' S? cp ( S? ',' S? cp )* S? ')'
   * </pre>
   *
   * @param elName The element type name this declaration is about.
   *
   * <strong>Note:</strong> Called after scanning past the first open paranthesis.
   */
  private final void scanChildren(String elName)
      throws IOException, XNIException {

    fContentDepth = 0;
    pushContentStack(0);
    int currentOp = 0;
    int c;
    while (true) {
      if (fEntityScanner.skipChar('(')) {
        fMarkUpDepth++;
        fStringBuffer.append('(');
        // call handler
        if (fDTDContentModelHandler != null) {
          fDTDContentModelHandler.startGroup(null);
        }
        // push current op on stack and reset it
        pushContentStack(currentOp);
        currentOp = 0;
        skipSeparator(false, !scanningInternalSubset());
        continue;
      }
      skipSeparator(false, !scanningInternalSubset());
      String childName = fEntityScanner.scanName();
      if (childName == null) {
        reportFatalError("MSG_OPEN_PAREN_OR_ELEMENT_TYPE_REQUIRED_IN_CHILDREN",
            new Object[]{elName});
        return;
      }
      // call handler
      if (fDTDContentModelHandler != null) {
        fDTDContentModelHandler.element(childName, null);
      }
      fStringBuffer.append(childName);
      c = fEntityScanner.peekChar();
      if (c == '?' || c == '*' || c == '+') {
        // call handler
        if (fDTDContentModelHandler != null) {
          short oc;
          if (c == '?') {
            oc = XMLDTDContentModelHandler.OCCURS_ZERO_OR_ONE;
          } else if (c == '*') {
            oc = XMLDTDContentModelHandler.OCCURS_ZERO_OR_MORE;
          } else {
            oc = XMLDTDContentModelHandler.OCCURS_ONE_OR_MORE;
          }
          fDTDContentModelHandler.occurrence(oc, null);
        }
        fEntityScanner.scanChar();
        fStringBuffer.append((char) c);
      }
      while (true) {
        skipSeparator(false, !scanningInternalSubset());
        c = fEntityScanner.peekChar();
        if (c == ',' && currentOp != '|') {
          currentOp = c;
          // call handler
          if (fDTDContentModelHandler != null) {
            fDTDContentModelHandler.separator(XMLDTDContentModelHandler.SEPARATOR_SEQUENCE,
                null);
          }
          fEntityScanner.scanChar();
          fStringBuffer.append(',');
          break;
        } else if (c == '|' && currentOp != ',') {
          currentOp = c;
          // call handler
          if (fDTDContentModelHandler != null) {
            fDTDContentModelHandler.separator(XMLDTDContentModelHandler.SEPARATOR_CHOICE,
                null);
          }
          fEntityScanner.scanChar();
          fStringBuffer.append('|');
          break;
        } else if (c != ')') {
          reportFatalError("MSG_CLOSE_PAREN_REQUIRED_IN_CHILDREN",
              new Object[]{elName});
        }
        // call handler
        if (fDTDContentModelHandler != null) {
          fDTDContentModelHandler.endGroup(null);
        }
        // restore previous op
        currentOp = popContentStack();
        short oc;
        // The following checks must be done in a single call (as
        // opposed to one for ')' and then one for '?', '*', and '+')
        // to guarantee that callbacks are properly nested. We do not
        // want to trigger endEntity too early in case we cross the
        // boundary of an entity between the two characters.
        if (fEntityScanner.skipString(")?")) {
          fStringBuffer.append(")?");
          // call handler
          if (fDTDContentModelHandler != null) {
            oc = XMLDTDContentModelHandler.OCCURS_ZERO_OR_ONE;
            fDTDContentModelHandler.occurrence(oc, null);
          }
        } else if (fEntityScanner.skipString(")+")) {
          fStringBuffer.append(")+");
          // call handler
          if (fDTDContentModelHandler != null) {
            oc = XMLDTDContentModelHandler.OCCURS_ONE_OR_MORE;
            fDTDContentModelHandler.occurrence(oc, null);
          }
        } else if (fEntityScanner.skipString(")*")) {
          fStringBuffer.append(")*");
          // call handler
          if (fDTDContentModelHandler != null) {
            oc = XMLDTDContentModelHandler.OCCURS_ZERO_OR_MORE;
            fDTDContentModelHandler.occurrence(oc, null);
          }
        } else {
          // no occurrence specified
          fEntityScanner.scanChar();
          fStringBuffer.append(')');
        }
        fMarkUpDepth--;
        if (fContentDepth == 0) {
          return;
        }
      }
      skipSeparator(false, !scanningInternalSubset());
    }
  }

  /**
   * Scans an attlist declaration
   * <p>
   * <pre>
   * [52]  AttlistDecl    ::=   '&lt;!ATTLIST' S Name AttDef* S? '>'
   * [53]  AttDef         ::=   S Name S AttType S DefaultDecl
   * </pre>
   * <p>
   * <strong>Note:</strong> Called after scanning past '&lt;!ATTLIST'
   */
  protected final void scanAttlistDecl() throws IOException, XNIException {

    // spaces
    fReportEntity = false;
    if (!skipSeparator(true, !scanningInternalSubset())) {
      reportFatalError("MSG_SPACE_REQUIRED_BEFORE_ELEMENT_TYPE_IN_ATTLISTDECL",
          null);
    }

    // element name
    String elName = fEntityScanner.scanName();
    if (elName == null) {
      reportFatalError("MSG_ELEMENT_TYPE_REQUIRED_IN_ATTLISTDECL",
          null);
    }

    // call handler
    if (fDTDHandler != null) {
      fDTDHandler.startAttlist(elName, null);
    }

    // spaces
    if (!skipSeparator(true, !scanningInternalSubset())) {
      // no space, is it the end yet?
      if (fEntityScanner.skipChar('>')) {
        // yes, stop here
        // call handler
        if (fDTDHandler != null) {
          fDTDHandler.endAttlist(null);
        }
        fMarkUpDepth--;
        return;
      } else {
        reportFatalError("MSG_SPACE_REQUIRED_BEFORE_ATTRIBUTE_NAME_IN_ATTDEF",
            new Object[]{elName});
      }
    }

    // definitions
    while (!fEntityScanner.skipChar('>')) {
      String name = fEntityScanner.scanName();
      if (name == null) {
        reportFatalError("AttNameRequiredInAttDef",
            new Object[]{elName});
      }
      // spaces
      if (!skipSeparator(true, !scanningInternalSubset())) {
        reportFatalError("MSG_SPACE_REQUIRED_BEFORE_ATTTYPE_IN_ATTDEF",
            new Object[]{elName, name});
      }
      // type
      String type = scanAttType(elName, name);

      // spaces
      if (!skipSeparator(true, !scanningInternalSubset())) {
        reportFatalError("MSG_SPACE_REQUIRED_BEFORE_DEFAULTDECL_IN_ATTDEF",
            new Object[]{elName, name});
      }

      // default decl
      String defaultType = scanAttDefaultDecl(elName, name,
          type,
          fLiteral, fLiteral2);
      // REVISIT: Should we do anything with the non-normalized
      //          default attribute value? -Ac
      // yes--according to bug 5073.  - neilg
      String[] enumr = null;
      if (fDTDHandler != null || nonValidatingMode) {
        if (fEnumerationCount != 0) {
          enumr = new String[fEnumerationCount];
          System.arraycopy(fEnumeration, 0, enumr,
              0, fEnumerationCount);
        }
      }
      // call handler
      // Determine whether the default value to be passed should be null.
      // REVISIT: should probably check whether fLiteral.ch is null instead. LM.
      if (defaultType != null && (defaultType.equals("#REQUIRED") ||
          defaultType.equals("#IMPLIED"))) {
        if (fDTDHandler != null) {
          fDTDHandler.attributeDecl(elName, name, type, enumr,
              defaultType, null, null, null);
        }
        if (nonValidatingMode) {
          nvGrammarInfo.attributeDecl(elName, name, type, enumr,
              defaultType, null, null, null);

        }
      } else {
        if (fDTDHandler != null) {
          fDTDHandler.attributeDecl(elName, name, type, enumr,
              defaultType, fLiteral, fLiteral2, null);
        }
        if (nonValidatingMode) {
          nvGrammarInfo.attributeDecl(elName, name, type, enumr,
              defaultType, fLiteral, fLiteral2, null);
        }
      }
      skipSeparator(false, !scanningInternalSubset());
    }

    // call handler
    if (fDTDHandler != null) {
      fDTDHandler.endAttlist(null);
    }
    fMarkUpDepth--;
    fReportEntity = true;

  } // scanAttlistDecl()

  /**
   * Scans an attribute type definition
   * <p>
   * <pre>
   * [54]  AttType        ::=   StringType | TokenizedType | EnumeratedType
   * [55]  StringType     ::=   'CDATA'
   * [56]  TokenizedType  ::=   'ID'
   *                          | 'IDREF'
   *                          | 'IDREFS'
   *                          | 'ENTITY'
   *                          | 'ENTITIES'
   *                          | 'NMTOKEN'
   *                          | 'NMTOKENS'
   * [57]  EnumeratedType ::=    NotationType | Enumeration
   * [58]  NotationType ::= 'NOTATION' S '(' S? Name (S? '|' S? Name)* S? ')'
   * [59]  Enumeration    ::=    '(' S? Nmtoken (S? '|' S? Nmtoken)* S? ')'
   * </pre>
   * <p>
   * <strong>Note:</strong> Called after scanning past '&lt;!ATTLIST'
   *
   * @param elName The element type name this declaration is about.
   * @param atName The attribute name this declaration is about.
   */
  private final String scanAttType(String elName, String atName)
      throws IOException, XNIException {

    String type = null;
    fEnumerationCount = 0;
        /*
         * Watchout: the order here is important: when a string happens to
         * be a substring of another string, the longer one needs to be
         * looked for first!!
         */
    if (fEntityScanner.skipString("CDATA")) {
      type = "CDATA";
    } else if (fEntityScanner.skipString("IDREFS")) {
      type = "IDREFS";
    } else if (fEntityScanner.skipString("IDREF")) {
      type = "IDREF";
    } else if (fEntityScanner.skipString("ID")) {
      type = "ID";
    } else if (fEntityScanner.skipString("ENTITY")) {
      type = "ENTITY";
    } else if (fEntityScanner.skipString("ENTITIES")) {
      type = "ENTITIES";
    } else if (fEntityScanner.skipString("NMTOKENS")) {
      type = "NMTOKENS";
    } else if (fEntityScanner.skipString("NMTOKEN")) {
      type = "NMTOKEN";
    } else if (fEntityScanner.skipString("NOTATION")) {
      type = "NOTATION";
      // spaces
      if (!skipSeparator(true, !scanningInternalSubset())) {
        reportFatalError("MSG_SPACE_REQUIRED_AFTER_NOTATION_IN_NOTATIONTYPE",
            new Object[]{elName, atName});
      }
      // open paren
      int c = fEntityScanner.scanChar();
      if (c != '(') {
        reportFatalError("MSG_OPEN_PAREN_REQUIRED_IN_NOTATIONTYPE",
            new Object[]{elName, atName});
      }
      fMarkUpDepth++;
      do {
        skipSeparator(false, !scanningInternalSubset());
        String aName = fEntityScanner.scanName();
        if (aName == null) {
          reportFatalError("MSG_NAME_REQUIRED_IN_NOTATIONTYPE",
              new Object[]{elName, atName});
        }
        ensureEnumerationSize(fEnumerationCount + 1);
        fEnumeration[fEnumerationCount++] = aName;
        skipSeparator(false, !scanningInternalSubset());
        c = fEntityScanner.scanChar();
      } while (c == '|');
      if (c != ')') {
        reportFatalError("NotationTypeUnterminated",
            new Object[]{elName, atName});
      }
      fMarkUpDepth--;
    } else {              // Enumeration
      type = "ENUMERATION";
      // open paren
      int c = fEntityScanner.scanChar();
      if (c != '(') {
        //                       "OPEN_PAREN_REQUIRED_BEFORE_ENUMERATION_IN_ATTRDECL",
        reportFatalError("AttTypeRequiredInAttDef",
            new Object[]{elName, atName});
      }
      fMarkUpDepth++;
      do {
        skipSeparator(false, !scanningInternalSubset());
        String token = fEntityScanner.scanNmtoken();
        if (token == null) {
          reportFatalError("MSG_NMTOKEN_REQUIRED_IN_ENUMERATION",
              new Object[]{elName, atName});
        }
        ensureEnumerationSize(fEnumerationCount + 1);
        fEnumeration[fEnumerationCount++] = token;
        skipSeparator(false, !scanningInternalSubset());
        c = fEntityScanner.scanChar();
      } while (c == '|');
      if (c != ')') {
        reportFatalError("EnumerationUnterminated",
            new Object[]{elName, atName});
      }
      fMarkUpDepth--;
    }
    return type;

  } // scanAttType():String


  /**
   * Scans an attribute default declaration
   * <p>
   * <pre>
   * [60] DefaultDecl ::= '#REQUIRED' | '#IMPLIED' | (('#FIXED' S)? AttValue)
   * </pre>
   *
   * @param name The name of the attribute being scanned.
   * @param defaultVal The string to fill in with the default value.
   */
  protected final String scanAttDefaultDecl(String elName, String atName,
      String type,
      XMLString defaultVal,
      XMLString nonNormalizedDefaultVal)
      throws IOException, XNIException {

    String defaultType = null;
    fString.clear();
    defaultVal.clear();
    if (fEntityScanner.skipString("#REQUIRED")) {
      defaultType = "#REQUIRED";
    } else if (fEntityScanner.skipString("#IMPLIED")) {
      defaultType = "#IMPLIED";
    } else {
      if (fEntityScanner.skipString("#FIXED")) {
        defaultType = "#FIXED";
        // spaces
        if (!skipSeparator(true, !scanningInternalSubset())) {
          reportFatalError("MSG_SPACE_REQUIRED_AFTER_FIXED_IN_DEFAULTDECL",
              new Object[]{elName, atName});
        }
      }
      // AttValue
      boolean isVC = !fStandalone && (fSeenExternalDTD || fSeenExternalPE);
      scanAttributeValue(defaultVal, nonNormalizedDefaultVal, atName,
          fAttributes, 0, isVC, elName);
    }
    return defaultType;

  } // ScanAttDefaultDecl

  /**
   * Scans an entity declaration
   * <p>
   * <pre>
   * [70]    EntityDecl  ::=    GEDecl | PEDecl
   * [71]    GEDecl      ::=    '&lt;!ENTITY' S Name S EntityDef S? '>'
   * [72]    PEDecl      ::=    '&lt;!ENTITY' S '%' S Name S PEDef S? '>'
   * [73]    EntityDef   ::=    EntityValue | (ExternalID NDataDecl?)
   * [74]    PEDef       ::=    EntityValue | ExternalID
   * [75]    ExternalID  ::=    'SYSTEM' S SystemLiteral
   *                          | 'PUBLIC' S PubidLiteral S SystemLiteral
   * [76]    NDataDecl   ::=    S 'NDATA' S Name
   * </pre>
   * <p>
   * <strong>Note:</strong> Called after scanning past '&lt;!ENTITY'
   */
  private final void scanEntityDecl() throws IOException, XNIException {

    boolean isPEDecl = false;
    boolean sawPERef = false;
    fReportEntity = false;
    if (fEntityScanner.skipSpaces()) {
      if (!fEntityScanner.skipChar('%')) {
        isPEDecl = false; // <!ENTITY x "x">
      } else if (skipSeparator(true, !scanningInternalSubset())) {
        // <!ENTITY % x "x">
        isPEDecl = true;
      } else if (scanningInternalSubset()) {
        reportFatalError("MSG_SPACE_REQUIRED_BEFORE_ENTITY_NAME_IN_ENTITYDECL",
            null);
        isPEDecl = true;
      } else if (fEntityScanner.peekChar() == '%') {
        // <!ENTITY %%x; "x"> is legal
        skipSeparator(false, !scanningInternalSubset());
        isPEDecl = true;
      } else {
        sawPERef = true;
      }
    } else if (scanningInternalSubset() || !fEntityScanner.skipChar('%')) {
      // <!ENTITY[^ ]...> or <!ENTITY[^ %]...>
      reportFatalError("MSG_SPACE_REQUIRED_BEFORE_ENTITY_NAME_IN_ENTITYDECL",
          null);
      isPEDecl = false;
    } else if (fEntityScanner.skipSpaces()) {
      // <!ENTITY% ...>
      reportFatalError("MSG_SPACE_REQUIRED_BEFORE_PERCENT_IN_PEDECL",
          null);
      isPEDecl = false;
    } else {
      sawPERef = true;
    }
    if (sawPERef) {
      while (true) {
        String peName = fEntityScanner.scanName();
        if (peName == null) {
          reportFatalError("NameRequiredInPEReference", null);
        } else if (!fEntityScanner.skipChar(';')) {
          reportFatalError("SemicolonRequiredInPEReference",
              new Object[]{peName});
        } else {
          startPE(peName, false);
        }
        fEntityScanner.skipSpaces();
        if (!fEntityScanner.skipChar('%')) {
          break;
        }
        if (!isPEDecl) {
          if (skipSeparator(true, !scanningInternalSubset())) {
            isPEDecl = true;
            break;
          }
          isPEDecl = fEntityScanner.skipChar('%');
        }
      }
    }

    // name
    String name = fEntityScanner.scanName();
    if (name == null) {
      reportFatalError("MSG_ENTITY_NAME_REQUIRED_IN_ENTITYDECL", null);
    }

    // spaces
    if (!skipSeparator(true, !scanningInternalSubset())) {
      reportFatalError("MSG_SPACE_REQUIRED_AFTER_ENTITY_NAME_IN_ENTITYDECL",
          new Object[]{name});
    }

    // external id
    scanExternalID(fStrings, false);
    String systemId = fStrings[0];
    String publicId = fStrings[1];

    if (isPEDecl && systemId != null) {
      fSeenExternalPE = true;
    }

    String notation = null;
    // NDATA
    boolean sawSpace = skipSeparator(true, !scanningInternalSubset());
    if (!isPEDecl && fEntityScanner.skipString("NDATA")) {
      // check whether there was space before NDATA
      if (!sawSpace) {
        reportFatalError("MSG_SPACE_REQUIRED_BEFORE_NDATA_IN_UNPARSED_ENTITYDECL",
            new Object[]{name});
      }

      // spaces
      if (!skipSeparator(true, !scanningInternalSubset())) {
        reportFatalError("MSG_SPACE_REQUIRED_BEFORE_NOTATION_NAME_IN_UNPARSED_ENTITYDECL",
            new Object[]{name});
      }
      notation = fEntityScanner.scanName();
      if (notation == null) {
        reportFatalError("MSG_NOTATION_NAME_REQUIRED_FOR_UNPARSED_ENTITYDECL",
            new Object[]{name});
      }
    }

    // internal entity
    if (systemId == null) {
      scanEntityValue(name, isPEDecl, fLiteral, fLiteral2);
      // since we need it's value anyway, let's snag it so it doesn't get corrupted
      // if a new load takes place before we store the entity values
      fStringBuffer.clear();
      fStringBuffer2.clear();
      fStringBuffer.append(fLiteral.ch, fLiteral.offset, fLiteral.length);
      fStringBuffer2.append(fLiteral2.ch, fLiteral2.offset, fLiteral2.length);
    }

    // skip possible trailing space
    skipSeparator(false, !scanningInternalSubset());

    // end
    if (!fEntityScanner.skipChar('>')) {
      reportFatalError("EntityDeclUnterminated", new Object[]{name});
    }
    fMarkUpDepth--;

    // register entity and make callback
    if (isPEDecl) {
      name = "%" + name;
    }
    if (systemId != null) {
      String baseSystemId = fEntityScanner.getBaseSystemId();
      if (notation != null) {
        fEntityStore.addUnparsedEntity(name, publicId, systemId, baseSystemId, notation);
      } else {
        fEntityStore.addExternalEntity(name, publicId, systemId,
            baseSystemId);
      }
      if (fDTDHandler != null) {
        //Venu Revisit : why false has been removed in expandSYstem
        fResourceIdentifier.setValues(publicId, systemId, baseSystemId,
            XMLEntityManager.expandSystemId(systemId, baseSystemId));

        if (notation != null) {
          fDTDHandler.unparsedEntityDecl(name, fResourceIdentifier,
              notation, null);
        } else {
          fDTDHandler.externalEntityDecl(name, fResourceIdentifier, null);
        }
      }
    } else {
      fEntityStore.addInternalEntity(name, fStringBuffer.toString());
      if (fDTDHandler != null) {
        fDTDHandler.internalEntityDecl(name, fStringBuffer, fStringBuffer2, null);
      }
    }
    fReportEntity = true;

  } // scanEntityDecl()

  /**
   * Scans an entity value.
   *
   * @param value The string to fill in with the value.
   * @param nonNormalizedValue The string to fill in with the non-normalized value.
   *
   * <strong>Note:</strong> This method uses fString, fStringBuffer (through the use of
   * scanCharReferenceValue), and fStringBuffer2, anything in them at the time of calling is lost.
   */
  protected final void scanEntityValue(String entityName, boolean isPEDecl, XMLString value,
      XMLString nonNormalizedValue)
      throws IOException, XNIException {
    int quote = fEntityScanner.scanChar();
    if (quote != '\'' && quote != '"') {
      reportFatalError("OpenQuoteMissingInDecl", null);
    }
    // store at which depth of entities we start
    int entityDepth = fEntityDepth;

    XMLString literal = fString;
    XMLString literal2 = fString;
    int countChar = 0;
    if (fLimitAnalyzer == null) {
      fLimitAnalyzer = new XMLLimitAnalyzer();
    }
    fLimitAnalyzer.startEntity(entityName);

    if (fEntityScanner.scanLiteral(quote, fString) != quote) {
      fStringBuffer.clear();
      fStringBuffer2.clear();
      do {
        if (isPEDecl && fLimitAnalyzer != null) {
          checkLimit("%" + entityName, fString.length + countChar);
        }
        countChar = 0;
        fStringBuffer.append(fString);
        fStringBuffer2.append(fString);
        if (fEntityScanner.skipChar('&')) {
          if (fEntityScanner.skipChar('#')) {
            fStringBuffer2.append("&#");
            scanCharReferenceValue(fStringBuffer, fStringBuffer2);
          } else {
            fStringBuffer.append('&');
            fStringBuffer2.append('&');
            String eName = fEntityScanner.scanName();
            if (eName == null) {
              reportFatalError("NameRequiredInReference",
                  null);
            } else {
              fStringBuffer.append(eName);
              fStringBuffer2.append(eName);
            }
            if (!fEntityScanner.skipChar(';')) {
              reportFatalError("SemicolonRequiredInReference",
                  new Object[]{eName});
            } else {
              fStringBuffer.append(';');
              fStringBuffer2.append(';');
            }
          }
        } else if (fEntityScanner.skipChar('%')) {
          while (true) {
            fStringBuffer2.append('%');
            String peName = fEntityScanner.scanName();
            if (peName == null) {
              reportFatalError("NameRequiredInPEReference",
                  null);
            } else if (!fEntityScanner.skipChar(';')) {
              reportFatalError("SemicolonRequiredInPEReference",
                  new Object[]{peName});
            } else {
              if (scanningInternalSubset()) {
                reportFatalError("PEReferenceWithinMarkup",
                    new Object[]{peName});
              }
              fStringBuffer2.append(peName);
              fStringBuffer2.append(';');
            }
            startPE(peName, true);
            // REVISIT: [Q] Why do we skip spaces here? -Ac
            // REVISIT: This will make returning the non-
            //          normalized value harder. -Ac
            fEntityScanner.skipSpaces();
            if (!fEntityScanner.skipChar('%')) {
              break;
            }
          }
        } else {
          countChar++;
          int c = fEntityScanner.peekChar();
          if (XMLChar.isHighSurrogate(c)) {
            scanSurrogates(fStringBuffer2);
          } else if (isInvalidLiteral(c)) {
            reportFatalError("InvalidCharInLiteral",
                new Object[]{Integer.toHexString(c)});
            fEntityScanner.scanChar();
          }
          // if it's not the delimiting quote or if it is but from a
          // different entity than the one this literal started from,
          // simply append the character to our buffer
          else if (c != quote || entityDepth != fEntityDepth) {
            fStringBuffer.append((char) c);
            fStringBuffer2.append((char) c);
            fEntityScanner.scanChar();
          }
        }
      } while (fEntityScanner.scanLiteral(quote, fString) != quote);
      fStringBuffer.append(fString);
      fStringBuffer2.append(fString);
      literal = fStringBuffer;
      literal2 = fStringBuffer2;
    } else {
      if (isPEDecl) {
        checkLimit("%" + entityName, literal);
      }
    }
    value.setValues(literal);
    nonNormalizedValue.setValues(literal2);
    if (fLimitAnalyzer != null) {
      fLimitAnalyzer.endEntity(XMLSecurityManager.Limit.PARAMETER_ENTITY_SIZE_LIMIT, entityName);
    }

    if (!fEntityScanner.skipChar(quote)) {
      reportFatalError("CloseQuoteMissingInDecl", null);
    }
  } // scanEntityValue(XMLString,XMLString):void

  /**
   * Scans a notation declaration
   * <p>
   * <pre>
   * [82] NotationDecl ::= '&lt;!NOTATION' S Name S (ExternalID|PublicID) S? '>'
   * [83]  PublicID    ::= 'PUBLIC' S PubidLiteral
   * </pre>
   * <p>
   * <strong>Note:</strong> Called after scanning past '&lt;!NOTATION'
   */
  private final void scanNotationDecl() throws IOException, XNIException {

    // spaces
    fReportEntity = false;
    if (!skipSeparator(true, !scanningInternalSubset())) {
      reportFatalError("MSG_SPACE_REQUIRED_BEFORE_NOTATION_NAME_IN_NOTATIONDECL",
          null);
    }

    // notation name
    String name = fEntityScanner.scanName();
    if (name == null) {
      reportFatalError("MSG_NOTATION_NAME_REQUIRED_IN_NOTATIONDECL",
          null);
    }

    // spaces
    if (!skipSeparator(true, !scanningInternalSubset())) {
      reportFatalError("MSG_SPACE_REQUIRED_AFTER_NOTATION_NAME_IN_NOTATIONDECL",
          new Object[]{name});
    }

    // external id
    scanExternalID(fStrings, true);
    String systemId = fStrings[0];
    String publicId = fStrings[1];
    String baseSystemId = fEntityScanner.getBaseSystemId();

    if (systemId == null && publicId == null) {
      reportFatalError("ExternalIDorPublicIDRequired",
          new Object[]{name});
    }

    // skip possible trailing space
    skipSeparator(false, !scanningInternalSubset());

    // end
    if (!fEntityScanner.skipChar('>')) {
      reportFatalError("NotationDeclUnterminated", new Object[]{name});
    }
    fMarkUpDepth--;

    fResourceIdentifier.setValues(publicId, systemId, baseSystemId,
        XMLEntityManager.expandSystemId(systemId, baseSystemId));
    if (nonValidatingMode) {
      nvGrammarInfo.notationDecl(name, fResourceIdentifier, null);
    }
    // call handler
    if (fDTDHandler != null) {
      //Venu Revisit wby false has been removed.
      //fResourceIdentifier.setValues(publicId, systemId, baseSystemId, XMLEntityManager.expandSystemId(systemId, baseSystemId, false));
      fDTDHandler.notationDecl(name, fResourceIdentifier, null);
    }
    fReportEntity = true;

  } // scanNotationDecl()

  /**
   * Scans a conditional section. If it's a section to ignore the whole
   * section gets scanned through and this method only returns after the
   * closing bracket has been found. When it's an include section though, it
   * returns to let the main loop take care of scanning it. In that case the
   * end of the section if handled by the main loop (scanDecls).
   * <p>
   * <pre>
   * [61] conditionalSect   ::= includeSect | ignoreSect
   * [62] includeSect       ::= '&lt;![' S? 'INCLUDE' S? '[' extSubsetDecl ']]>'
   * [63] ignoreSect   ::= '&lt;![' S? 'IGNORE' S? '[' ignoreSectContents* ']]>'
   * [64] ignoreSectContents ::= Ignore ('&lt;![' ignoreSectContents ']]>' Ignore)*
   * [65] Ignore            ::=    Char* - (Char* ('&lt;![' | ']]>') Char*)
   * </pre>
   * <p>
   * <strong>Note:</strong> Called after scanning past '&lt;!['
   */
  private final void scanConditionalSect(int currPEDepth)
      throws IOException, XNIException {

    fReportEntity = false;
    skipSeparator(false, !scanningInternalSubset());

    if (fEntityScanner.skipString("INCLUDE")) {
      skipSeparator(false, !scanningInternalSubset());
      if (currPEDepth != fPEDepth && fValidation) {
        fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
            "INVALID_PE_IN_CONDITIONAL",
            new Object[]{fEntityManager.fCurrentEntity.name},
            XMLErrorReporter.SEVERITY_ERROR);
      }
      // call handler
      if (!fEntityScanner.skipChar('[')) {
        reportFatalError("MSG_MARKUP_NOT_RECOGNIZED_IN_DTD", null);
      }

      if (fDTDHandler != null) {
        fDTDHandler.startConditional(XMLDTDHandler.CONDITIONAL_INCLUDE,
            null);
      }
      fIncludeSectDepth++;
      // just stop there and go back to the main loop
      fReportEntity = true;
    } else if (fEntityScanner.skipString("IGNORE")) {
      skipSeparator(false, !scanningInternalSubset());
      if (currPEDepth != fPEDepth && fValidation) {
        fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
            "INVALID_PE_IN_CONDITIONAL",
            new Object[]{fEntityManager.fCurrentEntity.name},
            XMLErrorReporter.SEVERITY_ERROR);
      }
      // call handler
      if (fDTDHandler != null) {
        fDTDHandler.startConditional(XMLDTDHandler.CONDITIONAL_IGNORE,
            null);
      }
      if (!fEntityScanner.skipChar('[')) {
        reportFatalError("MSG_MARKUP_NOT_RECOGNIZED_IN_DTD", null);
      }
      fReportEntity = true;
      int initialDepth = ++fIncludeSectDepth;
      if (fDTDHandler != null) {
        fIgnoreConditionalBuffer.clear();
      }
      while (true) {
        if (fEntityScanner.skipChar('<')) {
          if (fDTDHandler != null) {
            fIgnoreConditionalBuffer.append('<');
          }
          //
          // These tests are split so that we handle cases like
          // '<<![' and '<!<![' which we might otherwise miss.
          //
          if (fEntityScanner.skipChar('!')) {
            if (fEntityScanner.skipChar('[')) {
              if (fDTDHandler != null) {
                fIgnoreConditionalBuffer.append("![");
              }
              fIncludeSectDepth++;
            } else {
              if (fDTDHandler != null) {
                fIgnoreConditionalBuffer.append("!");
              }
            }
          }
        } else if (fEntityScanner.skipChar(']')) {
          if (fDTDHandler != null) {
            fIgnoreConditionalBuffer.append(']');
          }
          //
          // The same thing goes for ']<![' and '<]]>', etc.
          //
          if (fEntityScanner.skipChar(']')) {
            if (fDTDHandler != null) {
              fIgnoreConditionalBuffer.append(']');
            }
            while (fEntityScanner.skipChar(']')) {
                            /* empty loop body */
              if (fDTDHandler != null) {
                fIgnoreConditionalBuffer.append(']');
              }
            }
            if (fEntityScanner.skipChar('>')) {
              if (fIncludeSectDepth-- == initialDepth) {
                fMarkUpDepth--;
                // call handler
                if (fDTDHandler != null) {
                  fLiteral.setValues(fIgnoreConditionalBuffer.ch, 0,
                      fIgnoreConditionalBuffer.length - 2);
                  fDTDHandler.ignoredCharacters(fLiteral, null);
                  fDTDHandler.endConditional(null);
                }
                return;
              } else if (fDTDHandler != null) {
                fIgnoreConditionalBuffer.append('>');
              }
            }
          }
        } else {
          int c = fEntityScanner.scanChar();
          if (fScannerState == SCANNER_STATE_END_OF_INPUT) {
            reportFatalError("IgnoreSectUnterminated", null);
            return;
          }
          if (fDTDHandler != null) {
            fIgnoreConditionalBuffer.append((char) c);
          }
        }
      }
    } else {
      reportFatalError("MSG_MARKUP_NOT_RECOGNIZED_IN_DTD", null);
    }

  } // scanConditionalSect()

  /**
   * Dispatch an XML "event".
   *
   * @param complete True if this method is intended to scan and dispatch as much as possible.
   * @return True if there is more to scan.
   * @throws IOException Thrown on i/o error.
   * @throws XNIException Thrown on parse error.
   */
  protected final boolean scanDecls(boolean complete)
      throws IOException, XNIException {

    skipSeparator(false, true);
    boolean again = true;
    //System.out.println("scanDecls"+fScannerState);
    while (again && fScannerState == SCANNER_STATE_MARKUP_DECL) {
      again = complete;
      if (fEntityScanner.skipChar('<')) {
        fMarkUpDepth++;
        if (fEntityScanner.skipChar('?')) {
          fStringBuffer.clear();
          scanPI(fStringBuffer);
          fMarkUpDepth--; // we're done with this decl
        } else if (fEntityScanner.skipChar('!')) {
          if (fEntityScanner.skipChar('-')) {
            if (!fEntityScanner.skipChar('-')) {
              reportFatalError("MSG_MARKUP_NOT_RECOGNIZED_IN_DTD",
                  null);
            } else {
              scanComment();
            }
          } else if (fEntityScanner.skipString("ELEMENT")) {
            scanElementDecl();
          } else if (fEntityScanner.skipString("ATTLIST")) {
            scanAttlistDecl();
          } else if (fEntityScanner.skipString("ENTITY")) {
            scanEntityDecl();
          } else if (fEntityScanner.skipString("NOTATION")) {
            scanNotationDecl();
          } else if (fEntityScanner.skipChar('[') &&
              !scanningInternalSubset()) {
            scanConditionalSect(fPEDepth);
          } else {
            fMarkUpDepth--;
            reportFatalError("MSG_MARKUP_NOT_RECOGNIZED_IN_DTD",
                null);
          }
        } else {
          fMarkUpDepth--;
          reportFatalError("MSG_MARKUP_NOT_RECOGNIZED_IN_DTD", null);
        }
      } else if (fIncludeSectDepth > 0 && fEntityScanner.skipChar(']')) {
        // end of conditional section?
        if (!fEntityScanner.skipChar(']')
            || !fEntityScanner.skipChar('>')) {
          reportFatalError("IncludeSectUnterminated", null);
        }
        // call handler
        if (fDTDHandler != null) {
          fDTDHandler.endConditional(null);
        }
        // decreaseMarkupDepth();
        fIncludeSectDepth--;
        fMarkUpDepth--;
      } else if (scanningInternalSubset() &&
          fEntityScanner.peekChar() == ']') {
        // this is the end of the internal subset, let's stop here
        return false;
      } else if (fEntityScanner.skipSpaces()) {
        // simply skip
      } else {
        reportFatalError("MSG_MARKUP_NOT_RECOGNIZED_IN_DTD", null);
      }
      skipSeparator(false, true);
    }
    return fScannerState != SCANNER_STATE_END_OF_INPUT;
  }

  /**
   * Skip separator. This is typically just whitespace but it can also be one
   * or more parameter entity references.
   * <p>
   * If there are some it "expands them" by calling the corresponding entity
   * from the entity manager.
   * <p>
   * This is recursive and will process has many refs as possible.
   *
   * @param spaceRequired Specify whether some leading whitespace should be found
   * @param lookForPERefs Specify whether parameter entity references should be looked for
   * @return True if any leading whitespace was found or the end of a parameter entity was crossed.
   */
  private boolean skipSeparator(boolean spaceRequired, boolean lookForPERefs)
      throws IOException, XNIException {
    int depth = fPEDepth;
    boolean sawSpace = fEntityScanner.skipSpaces();
    if (!lookForPERefs || !fEntityScanner.skipChar('%')) {
      return !spaceRequired || sawSpace || (depth != fPEDepth);
    }
    while (true) {
      String name = fEntityScanner.scanName();
      if (name == null) {
        reportFatalError("NameRequiredInPEReference", null);
      } else if (!fEntityScanner.skipChar(';')) {
        reportFatalError("SemicolonRequiredInPEReference",
            new Object[]{name});
      }
      startPE(name, false);
      fEntityScanner.skipSpaces();
      if (!fEntityScanner.skipChar('%')) {
        return true;
      }
    }
  }


  /*
   * Element Children Content Stack
   */
  private final void pushContentStack(int c) {
    if (fContentStack.length == fContentDepth) {
      int[] newStack = new int[fContentDepth * 2];
      System.arraycopy(fContentStack, 0, newStack, 0, fContentDepth);
      fContentStack = newStack;
    }
    fContentStack[fContentDepth++] = c;
  }

  private final int popContentStack() {
    return fContentStack[--fContentDepth];
  }


  /*
   * Parameter Entity Stack
   */
  private final void pushPEStack(int depth, boolean report) {
    if (fPEStack.length == fPEDepth) {
      int[] newIntStack = new int[fPEDepth * 2];
      System.arraycopy(fPEStack, 0, newIntStack, 0, fPEDepth);
      fPEStack = newIntStack;
      // report end/start calls
      boolean[] newBooleanStack = new boolean[fPEDepth * 2];
      System.arraycopy(fPEReport, 0, newBooleanStack, 0, fPEDepth);
      fPEReport = newBooleanStack;

    }
    fPEReport[fPEDepth] = report;
    fPEStack[fPEDepth++] = depth;
  }

  /**
   * pop the stack
   */
  private final int popPEStack() {
    return fPEStack[--fPEDepth];
  }

  /**
   * look at the top of the stack
   */
  private final boolean peekReportEntity() {
    return fPEReport[fPEDepth - 1];
  }


  /*
   * Utility method
   */
  private final void ensureEnumerationSize(int size) {
    if (fEnumeration.length == size) {
      String[] newEnum = new String[size * 2];
      System.arraycopy(fEnumeration, 0, newEnum, 0, size);
      fEnumeration = newEnum;
    }
  }

  // private methods
  private void init() {
    // reset state related data
    fStartDTDCalled = false;
    fExtEntityDepth = 0;
    fIncludeSectDepth = 0;
    fMarkUpDepth = 0;
    fPEDepth = 0;

    fStandalone = false;
    fSeenExternalDTD = false;
    fSeenExternalPE = false;

    // set starting state
    setScannerState(SCANNER_STATE_TEXT_DECL);
    //new SymbolTable());

    fLimitAnalyzer = new XMLLimitAnalyzer();
  }

  /**
   * Add the count of the content buffer and check if the accumulated
   * value exceeds the limit
   *
   * @param entityName entity name
   * @param buffer content buffer
   */
  private void checkLimit(String entityName, XMLString buffer) {
    checkLimit(entityName, buffer.length);
  }

  /**
   * Add the count and check limit
   *
   * @param entityName entity name
   * @param len length of the buffer
   */
  private void checkLimit(String entityName, int len) {
    if (fLimitAnalyzer == null) {
      fLimitAnalyzer = new XMLLimitAnalyzer();
    }
    fLimitAnalyzer.addValue(XMLSecurityManager.Limit.PARAMETER_ENTITY_SIZE_LIMIT, entityName, len);
    if (fSecurityManager
        .isOverLimit(XMLSecurityManager.Limit.PARAMETER_ENTITY_SIZE_LIMIT, fLimitAnalyzer)) {
      fSecurityManager.debugPrint(fLimitAnalyzer);
      reportFatalError("MaxEntitySizeLimit", new Object[]{entityName,
          fLimitAnalyzer.getValue(XMLSecurityManager.Limit.PARAMETER_ENTITY_SIZE_LIMIT),
          fSecurityManager.getLimit(XMLSecurityManager.Limit.PARAMETER_ENTITY_SIZE_LIMIT),
          fSecurityManager.getStateLiteral(XMLSecurityManager.Limit.PARAMETER_ENTITY_SIZE_LIMIT)});
    }
    if (fSecurityManager
        .isOverLimit(XMLSecurityManager.Limit.TOTAL_ENTITY_SIZE_LIMIT, fLimitAnalyzer)) {
      fSecurityManager.debugPrint(fLimitAnalyzer);
      reportFatalError("TotalEntitySizeLimit",
          new Object[]{
              fLimitAnalyzer.getTotalValue(XMLSecurityManager.Limit.TOTAL_ENTITY_SIZE_LIMIT),
              fSecurityManager.getLimit(XMLSecurityManager.Limit.TOTAL_ENTITY_SIZE_LIMIT),
              fSecurityManager.getStateLiteral(XMLSecurityManager.Limit.TOTAL_ENTITY_SIZE_LIMIT)});
    }

  }

  public DTDGrammar getGrammar() {
    return nvGrammarInfo;
  }

} // class XMLDTDScannerImpl
